package com.spzx.cart.service.impl;

import com.spzx.cart.api.domain.CartInfo;
import com.spzx.cart.service.ICartService;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.context.SecurityContextHolder;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.product.api.RemoteProductService;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.vo.SkuPrice;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Slf4j
public class CartServiceImpl implements ICartService {

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    private RemoteProductService remoteProductService;

    private String getCartKey(Long userId) {
        //定义key user:userId:cart
        return "user:cart:" + userId;
    }


        /**
     * 将商品添加到购物车
     *
     * 该方法实现商品添加到购物车的功能，包括两种情况：
     * 1. 如果购物车中已存在该商品，则增加商品数量
     * 2. 如果购物车中不存在该商品，则新增商品信息到购物车
     * 同时处理商品数量限制、购物车容量限制和商品信息获取等逻辑
     *
     * @param skuId 商品SKU唯一标识，用于定位具体商品
     * @param skuNum 要添加的商品数量
     */
    @Override
    public void addToCart(Long skuId, Integer skuNum) {
        // 调用SecurityContextHolder.getUserId()静态方法获取当前登录用户的唯一标识ID
        // 目的是确定商品需要添加到哪个用户的购物车中，确保数据隔离和准确性
        Long userId = SecurityContextHolder.getUserId();

        // 调用getCartKey(userId)私有方法构建该用户购物车在Redis中的存储键名
        // 键名格式为"user:cart:用户ID"，确保每个用户的购物车数据都有独立的存储空间
        String cartKey = getCartKey(userId);

        // 调用redisTemplate.boundHashOps(cartKey)方法获取Redis中购物车Hash结构的操作对象
        // BoundHashOperations提供了对Redis Hash数据结构的便捷操作接口，用于操作购物车中的商品数据
        // 泛型参数<String,String,CartInfo>分别表示HashKey类型、Hash的field类型和Hash的value类型
        BoundHashOperations<String,String, CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);

        // 将Long类型的skuId转换为String类型，作为Hash结构中的字段名使用
        String hashkey = skuId.toString();

        // 定义商品数量上限阈值为99，防止购物车中单个商品数量过多
        Integer thresHold = 99;

        // 调用hashOps.hasKey(hashkey)方法检查购物车Hash结构中是否已存在指定SKU ID的商品
        // 如果存在则执行数量增加逻辑，避免重复添加相同商品
        if(hashOps.hasKey(hashkey)){
            // 调用hashOps.get(hashkey)方法从购物车Hash结构中获取指定SKU ID对应的商品信息
            // 返回CartInfo对象，包含该商品的完整购物车信息
            CartInfo cartInfo = hashOps.get(hashkey);

            // 计算新增后的商品总数量：当前数量 + 新增数量
            int totalCount = cartInfo.getSkuNum() + skuNum;

            // 使用三元运算符判断总数量是否超过阈值，如果超过则设置为阈值，否则使用实际总数量
            // 实现商品数量的上限控制，防止购物车中单个商品数量过多
            cartInfo.setSkuNum(totalCount>thresHold?thresHold:totalCount);

            // 调用hashOps.put(hashkey,cartInfo)方法将更新后的商品信息重新保存到Redis购物车中
            // 完成商品数量的更新操作
            hashOps.put(hashkey,cartInfo);
        }else {
            // 如果购物车中不存在该商品，则执行新增商品逻辑

            // 调用hashOps.size()方法获取当前购物车中商品的总数量
            // 用于检查购物车是否已达到最大容量限制
            Long count = hashOps.size();

            // 判断购物车商品数量是否达到上限50个，如果达到则抛出异常阻止继续添加
            // 实现购物车整体容量的限制控制
            if(count>=50){
                throw new RuntimeException("购物车数量已达到最大值");
            }

            // 创建新的CartInfo对象，用于存储新增商品的购物车信息
            CartInfo cartInfo = new CartInfo();

            // 调用cartInfo.setSkuId(skuId)方法设置商品SKU ID
            cartInfo.setSkuId(skuId);

            // 使用三元运算符设置商品数量，如果超过阈值则设置为阈值，否则使用实际数量
            cartInfo.setSkuNum(skuNum>thresHold?thresHold:skuNum);

            // 调用new Date()创建当前时间对象，并设置商品的创建时间和更新时间
            // 记录商品加入购物车的时间信息
            cartInfo.setCreateTime(new Date());
            cartInfo.setUpdateTime(new Date());

            // 设置商品默认选中状态为1(选中)，方便用户直接结算
            cartInfo.setIsChecked(1);

            // 调用remoteProductService.getProductSku(skuId, SecurityConstants.INNER)远程接口
            // 获取商品SKU的详细信息，SecurityConstants.INNER表示内部调用
            // 返回R<ProductSku>类型，包含商品信息和调用状态
            R<ProductSku> productSku = remoteProductService.getProductSku(skuId, SecurityConstants.INNER);

            // 检查远程调用是否失败，如果返回码为R.FAIL则抛出ServiceException异常
            // 确保在商品信息获取失败时能及时处理异常情况
            if (R.FAIL == productSku.getCode()) {
                throw new ServiceException(productSku.getMsg());
            }

            // 从R<ProductSku>结果对象中获取实际的商品SKU数据
            ProductSku productSkuData = productSku.getData();

            // 调用相应setter方法设置商品的名称、价格、图片等基本信息
            // 完善购物车中商品的展示信息
            cartInfo.setSkuName(productSkuData.getSkuName());
            cartInfo.setCartPrice(productSkuData.getSalePrice());
            cartInfo.setThumbImg(productSkuData.getThumbImg());
            cartInfo.setSkuPrice(productSkuData.getSalePrice());

            // 调用hashOps.put(hashkey,cartInfo)方法将新商品信息保存到Redis购物车Hash结构中
            // 完成新商品的添加操作
            hashOps.put(hashkey,cartInfo);
        }
        // 通过以上操作，实现了商品添加到购物车的完整功能，包括数量控制、容量限制和信息获取
    }



            /**
     * 获取用户购物车列表
     *
     * 该方法用于获取当前登录用户的完整购物车信息，包括所有商品的详细信息。
     * 方法会从Redis中获取用户的购物车数据，按创建时间倒序排序，并更新商品的实时价格信息。
     *
     * @return 购物车信息列表，按创建时间倒序排列，如果购物车为空则返回空列表
     */
    @Override
    public List<CartInfo> cartList() {
        // 调用SecurityContextHolder.getUserId()静态方法获取当前登录用户的唯一标识ID
        // 目的是确定需要获取哪个用户的购物车数据，确保数据的准确性和隔离性
        Long userId = SecurityContextHolder.getUserId();

        // 调用getCartKey(userId)私有方法构建该用户购物车在Redis中的存储键名
        // 键名格式为"user:cart:用户ID"，确保每个用户的购物车数据都有独立的存储空间
        String cartKey = getCartKey(userId);

        // 调用redisTemplate.boundHashOps(cartKey)方法获取Redis中购物车Hash结构的操作对象
        // BoundHashOperations提供了对Redis Hash数据结构的便捷操作接口，用于获取购物车中的商品数据
        BoundHashOperations hashOps = redisTemplate.boundHashOps(cartKey);

        // 调用hashOps.values()方法获取购物车Hash结构中所有的商品信息值
        // 返回List<CartInfo>类型，包含购物车中所有商品的完整信息
        List<CartInfo> cartInfoList = hashOps.values();

        // 调用CollectionUtils.isEmpty(cartInfoList)方法检查购物车列表是否为空
        // 避免直接使用cartInfoList.isEmpty()可能出现的空指针异常
        // 如果购物车列表不为空，则执行后续的排序和价格更新操作
        if(!CollectionUtils.isEmpty(cartInfoList)){
            // 使用Stream API对购物车商品列表按创建时间进行倒序排序
            // 调用cartInfoList.stream()创建流，sorted()方法进行排序，(o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime())
            // 是自定义比较器，按创建时间倒序排列(最新的在前)，collect(Collectors.toList())收集结果为新的列表
            List<CartInfo> infoList = cartInfoList.stream().sorted((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()))
                    .collect(Collectors.toList());

            // 使用Stream API提取购物车中所有商品的SKU ID，构建需要查询价格的SKU ID列表
            // 调用infoList.stream()创建流，map(CartInfo::getUserId)提取每个商品的用户ID(这里应该是getSkuId，代码有误)
            // collect(Collectors.toList())收集结果为List<Long>类型
            List<Long> skuIdList = infoList.stream().map(CartInfo::getSkuId).collect(Collectors.toList());

            // 调用remoteProductService.getSkuPriceList(skuIdList, SecurityConstants.INNER)远程接口
            // 获取购物车中所有商品的实时价格信息，SecurityConstants.INNER表示内部调用
            // 返回R<List<SkuPrice>>类型，包含价格查询结果和状态信息
            R<List<SkuPrice>> skuPriceList = remoteProductService.getSkuPriceList(skuIdList, SecurityConstants.INNER);

            // 检查远程调用是否失败，如果返回码为R.FAIL则抛出ServiceException异常
            // 确保在价格查询失败时能及时处理异常情况
            if (R.FAIL == skuPriceList.getCode()) {
                throw new ServiceException(skuPriceList.getMsg());
            }

            // 使用Stream API将SKU价格列表转换为SKU ID到价格的映射关系Map
            // 调用skuPriceList.getData().stream()创建流，collect(Collectors.toMap())收集为Map
            // SkuPrice::getSkuId作为key映射函数，SkuPrice::getSalePrice作为value映射函数
            Map<Long, BigDecimal> skuIdToPriceMap = skuPriceList.getData().stream()
                    .collect(Collectors.toMap(SkuPrice::getSkuId, SkuPrice::getSalePrice));

            // 使用forEach遍历购物车商品列表，为每个商品设置最新的实时价格
            // 通过skuIdToPriceMap.get(cartInfo.getSkuId())获取对应SKU的最新价格
            // 调用cartInfo.setSkuPrice()方法更新商品价格信息
            infoList.forEach(cartInfo -> {
                cartInfo.setSkuPrice(skuIdToPriceMap.get(cartInfo.getSkuId()));
            });

            // 返回处理完成的购物车商品列表，包含排序后的商品和最新的价格信息
            return infoList;
        }

        // 如果购物车为空，返回一个新的空ArrayList实例，避免返回null导致空指针异常
        return new ArrayList<>();
    }



        /**
     * 删除购物车中的指定商品
     * @param skuId 商品SKU ID
     */
    @Override
    public void deleteCart(Long skuId) {
        // 获取当前登录用户ID
        Long userId = SecurityContextHolder.getUserId();
        // 构造购物车在Redis中的存储键
        String cartKey = getCartKey(userId);

        // 从Redis中删除指定SKU的商品信息
        redisTemplate.boundHashOps(cartKey).delete(skuId.toString());
    }


        /**
     * 更新购物车中指定商品的选中状态
     *
     * 该方法用于处理购物车页面中单个商品的选中或取消选中操作。
     * 通过获取指定SKU商品的信息，更新其选中状态，并将更新后的信息保存回Redis购物车中。
     *
     * @param skuId 商品SKU唯一标识，用于定位需要更新的购物车商品
     * @param isChecked 选中状态标识，1表示选中该商品，0表示取消选中该商品
     */
    @Override
    public void checkCart(Long skuId, Integer isChecked) {
        // 调用SecurityContextHolder.getUserId()静态方法获取当前登录用户的唯一标识ID
        // 目的是确定操作的是哪个用户的购物车数据，确保数据隔离和操作准确性
        Long userId = SecurityContextHolder.getUserId();

        // 调用getCartKey(userId)私有方法构建该用户购物车在Redis中的存储键名
        // 键名格式为"user:cart:用户ID"，确保每个用户的购物车数据都有独立的存储空间
        String cartKey = getCartKey(userId);

        // 调用redisTemplate.boundHashOps(cartKey)方法获取Redis中购物车Hash结构的操作对象
        // BoundHashOperations提供了对Redis Hash数据结构的便捷操作接口，专门用于操作购物车中的商品数据
        // 泛型参数<String,String,CartInfo>分别表示HashKey类型、Hash的field类型和Hash的value类型
        BoundHashOperations<String,String, CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);

        // 调用hashOps.hasKey(skuId.toString())方法检查购物车Hash结构中是否存在指定SKU ID的字段
        // 将Long类型的skuId转换为String类型作为Hash的field进行查找，确保能正确匹配到对应商品
        if(hashOps.hasKey(skuId.toString())){
            // 调用hashOps.get(skuId.toString())方法从购物车Hash结构中获取指定SKU ID对应的商品信息
            // 返回CartInfo对象，包含该商品的完整购物车信息
            CartInfo cartInfo = hashOps.get(skuId.toString());

            // 调用cartInfo.setIsChecked(isChecked)方法设置商品的选中状态
            // 将商品对象的isChecked属性更新为传入的isChecked值(1表示选中，0表示未选中)
            cartInfo.setIsChecked(isChecked);

            // 调用hashOps.put(skuId.toString(),cartInfo)方法将更新后的商品信息重新保存到Redis购物车中
            // 第一个参数是Hash的field(字段名)，使用商品SKU ID作为唯一标识
            // 第二个参数是Hash的value(字段值)，即更新选中状态后的完整CartInfo对象
            // 通过覆盖原有数据完成商品选中状态的持久化更新
            hashOps.put(skuId.toString(),cartInfo);
        }
        // 如果指定SKU ID的商品不存在于购物车中，则不执行任何操作，直接结束方法
        // 通过以上操作，实现了购物车中单个商品选中状态的精确更新，保证了购物车数据的一致性
    }



            /**
     * 全选或取消全选购物车中的所有商品
     *
     * 该方法实现购物车商品的批量选中或取消选中功能，用于支持购物车页面的"全选"和"取消全选"操作。
     * 通过遍历购物车中的所有商品，统一设置其选中状态。
     *
     * @param isChecked 选中状态标识，1表示选中商品，0表示取消选中
     */
    @Override
    public void allCheckCart(Integer isChecked) {
        // 调用SecurityContextHolder.getUserId()静态方法获取当前登录用户的唯一标识ID
        // 目的是确定操作的是哪个用户的购物车数据，确保操作的准确性和数据隔离
        Long userId = SecurityContextHolder.getUserId();

        // 调用getCartKey(userId)私有方法构建该用户购物车在Redis中的存储键名
        // 键名格式为"user:cart:用户ID"，确保每个用户的购物车数据独立存储，避免不同用户间的数据混乱
        String cartKey = getCartKey(userId);

        // 调用redisTemplate.boundHashOps(cartKey)方法获取Redis中购物车Hash结构的操作对象
        // BoundHashOperations提供了对Redis Hash数据结构的便捷操作接口，用于操作购物车中的商品数据
        // 泛型参数<String,String,CartInfo>分别表示: HashKey类型、Hash的field类型、Hash的value类型
        BoundHashOperations<String,String, CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);

        // 调用hashOps.values()方法获取购物车中所有的商品信息列表，返回List<CartInfo>类型
        // 然后使用forEach()方法遍历每个CartInfo对象，对每个商品执行相同的状态更新操作
        // forEach是Java 8的Stream API方法，用于对集合中的每个元素执行指定操作
        hashOps.values().forEach(cartInfo -> {
            // 调用cartInfo.setIsChecked(isChecked)方法设置当前商品的选中状态
            // 将商品的isChecked属性更新为传入的isChecked值(1表示选中，0表示未选中)
            // 这实现了对单个商品选中状态的更新
            cartInfo.setIsChecked(isChecked);

            // 调用hashOps.put(cartInfo.getSkuId().toString(),cartInfo)方法将更新后的商品信息
            // 重新保存到Redis购物车Hash结构中，覆盖原来的商品数据，完成状态更新的持久化存储
            // 第一个参数是Hash的field(字段名)，使用商品SKU ID作为唯一标识
            // 第二个参数是Hash的value(字段值)，即更新后的完整CartInfo对象
            hashOps.put(cartInfo.getSkuId().toString(),cartInfo);
        });
        // 通过以上操作，实现了购物车中所有商品的选中状态统一更新，完成全选或取消全选功能
        // 确保了购物车界面与后端数据的一致性，为后续的结算等操作提供准确的数据基础
    }



        /**
     * 清空用户购物车
     *
     * 该方法用于清空指定用户的整个购物车数据，通过删除Redis中存储的购物车键来实现
     * 购物车数据的完全清除功能。
     */
    @Override
    public void clearCart() {
        // 调用SecurityContextHolder.getUserId()方法获取当前登录用户的唯一标识ID
        // 目的是确定需要清空哪个用户的购物车数据
        Long userId = SecurityContextHolder.getUserId();

        // 调用getCartKey(userId)方法，根据用户ID构建该用户购物车在Redis中的存储键
        // 键的格式为"user:cart:用户ID"，确保每个用户的购物车数据独立存储
        String cartKey = getCartKey(userId);

        // 调用redisTemplate.delete(cartKey)方法，删除Redis中对应的购物车键
        // 这将彻底清除该用户的所有购物车商品信息，实现清空购物车的功能
        redisTemplate.delete(cartKey);
    }


    @Override
    public List<CartInfo> getCartCheckedList(Long userId) {
        List<CartInfo> cartInfoList = new ArrayList<>();

        String cartKey = getCartKey(userId);
        List<CartInfo> cartInfoList1 = cartList();

        if(!CollectionUtils.isEmpty(cartInfoList1)){
            for (CartInfo cartInfo : cartInfoList1) {
                if(cartInfo.getIsChecked() == 1){
                    cartInfoList.add(cartInfo);
                }
            }
        }
        return cartInfoList;
    }


        /**
     * 更新用户购物车中商品的价格
     *
     * 该方法用于同步购物车中所有商品的最新价格信息，确保用户在结算时看到的是最新的商品价格。
     * 通过批量获取购物车中所有商品的实时价格，并更新购物车中对应商品的价格数据。
     *
     * @param userId 用户ID，用于定位特定用户的购物车数据
     * @return 更新成功返回true，表示所有商品价格已同步更新
     * @throws ServiceException 当获取商品价格信息失败时抛出业务异常
     */
    @Override
    public Boolean updateCartPrice(Long userId) {
        // 调用getCartKey方法构造该用户购物车在Redis中的存储键名
        // 格式为"user:cart:用户ID"，用于定位特定用户的购物车数据
        String cartKey = getCartKey(userId);

        // 获取Redis中购物车Hash结构的操作对象，用于后续对购物车数据的读写操作
        // BoundHashOperations提供了对Redis Hash数据结构的便捷操作接口
        BoundHashOperations hashOps = redisTemplate.boundHashOps(cartKey);

        // 从Redis购物车Hash结构中获取所有商品信息列表
        // values()方法返回List<CartInfo>类型，包含购物车中所有商品的完整信息
        List<CartInfo> cartInfoList = hashOps.values();

        // 检查购物车是否为空，避免对空购物车进行不必要的价格更新操作
        // 使用CollectionUtils.isEmpty进行空值检查，防止空指针异常
        if(!CollectionUtils.isEmpty(cartInfoList)){
            // 使用Stream API提取购物车中所有商品的SKU ID，构建需要查询价格的SKU ID列表
            // map(CartInfo::getSkuId)提取每个商品的SKU ID，toList()收集为List<Long>类型
            // 用于后续批量查询这些商品的实时价格
            List<Long> idList = cartInfoList.stream().map(CartInfo::getSkuId).toList();

            // 调用远程商品服务接口，批量获取购物车中所有商品的最新价格信息
            // 传入SKU ID列表和内部调用标识，获取R<List<SkuPrice>>类型的响应结果
            R<List<SkuPrice>> skuPriceList = remoteProductService.getSkuPriceList(idList,SecurityConstants.INNER);

            // 检查远程调用是否失败，如果返回码为R.FAIL则抛出ServiceException异常
            // 确保在价格查询失败时能及时处理异常情况，避免使用错误的价格数据
            if(R.FAIL==skuPriceList.getCode()){
                throw new ServiceException(skuPriceList.getMsg());
            }

            // 从远程调用结果中提取实际的商品价格数据列表
            // getData()方法返回List<SkuPrice>类型，包含所有查询商品的价格信息
            List<SkuPrice> skuPriceListData = skuPriceList.getData();

            // 使用Stream API将SKU价格列表转换为SKU ID到销售价格的映射关系Map
            // collect(Collectors.toMap())收集为Map，SkuPrice::getSkuId作为key，SkuPrice::getSalePrice作为value
            // 便于后续通过SKU ID快速查找对应的商品价格
            Map<Long, BigDecimal> SkuIdToSalePriceMap = skuPriceListData.stream()
                    .collect(Collectors.toMap(SkuPrice::getSkuId, SkuPrice::getSalePrice));

            // 遍历购物车中的每个商品，更新其价格信息
            for (CartInfo cartInfo : cartInfoList) {
                // 从价格映射表中获取当前商品的最新销售价格，并更新购物车商品的价格字段
                // setCartPrice方法设置商品在购物车中的价格，保持与最新价格同步
                cartInfo.setCartPrice(SkuIdToSalePriceMap.get(cartInfo.getSkuId()));

                // 将更新了价格信息的商品重新保存到Redis购物车Hash结构中
                // 覆盖原有数据，完成价格更新的持久化存储
                // 第一个参数是Hash的field(字段名)，使用商品SKU ID作为唯一标识
                // 第二个参数是Hash的value(字段值)，即更新价格后的完整CartInfo对象
                hashOps.put(cartInfo.getSkuId().toString(),cartInfo);
            }
        }

        // 返回true表示价格更新操作成功完成
        return true;
    }


        /**
     * 删除用户购物车中已选中的商品
     *
     * 该方法用于清理用户购物车中已经被选中的商品，通常在用户完成订单支付后调用，
     * 以清除已购买的商品，避免用户重复下单。
     *
     * @param userId 用户ID，用于定位特定用户的购物车数据
     * @return 删除成功返回true，表示已选中商品已从购物车中移除
     */
    @Override
    public Boolean deleteCartCheckedList(Long userId) {
        // 调用getCartKey方法构造该用户购物车在Redis中的存储键名
        // 格式为"user:cart:用户ID"，用于定位特定用户的购物车数据
        String cartKey = getCartKey(userId);

        // 获取Redis中购物车Hash结构的操作对象，用于后续对购物车数据的读写操作
        // BoundHashOperations提供了对Redis Hash数据结构的便捷操作接口
        // 泛型参数<String,String,CartInfo>分别表示HashKey类型、Hash的field类型和Hash的value类型
        BoundHashOperations<String, String, CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);

        // 从Redis购物车Hash结构中获取所有商品信息列表
        // values()方法返回List<CartInfo>类型，包含购物车中所有商品的完整信息
        List<CartInfo> cartInfoList = hashOps.values();

        // 检查购物车是否为空，避免对空购物车进行不必要的删除操作
        // 使用CollectionUtils.isEmpty进行空值检查，防止空指针异常
        if(!CollectionUtils.isEmpty(cartInfoList)){
            // 遍历购物车中的每个商品，检查其选中状态
            for (CartInfo cartInfo : cartInfoList) {
                // 判断当前商品是否被选中（isChecked值为1表示选中）
                // 只有选中的商品才需要被删除，未选中的商品保留在购物车中
                if(cartInfo.getIsChecked()==1){
                    // 从Redis购物车Hash结构中删除指定SKU ID的商品
                    // delete方法根据Hash的field(字段名)删除对应的键值对
                    // 将Long类型的skuId转换为String类型作为Hash的field进行删除操作
                    // 完成已选中商品的清理工作
                    hashOps.delete(cartInfo.getSkuId().toString());
                }
            }
        }

        // 返回true表示删除操作成功完成（即使购物车为空也返回true）
        return true;
    }

}